/**
 * 版权所有(C)，上海海鼎信息工程股份有限公司，2009，所有权利保留。
 * 
 * 项目名：	rumba-widget2
 * 文件名：	RComboBox.java
 * 模块说明：	
 * 修改历史：
 * Feb 5, 2009 - lxm - 创建。
 */
package com.hd123.rumba.gwt.widget2.client.form;

import java.util.ArrayList;
import java.util.Date;

import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.Widget;
import com.hd123.gwt.types.JsBigDecimal;
import com.hd123.rumba.gwt.widget2.client.button.IsRButton;
import com.hd123.rumba.gwt.widget2.client.fielddef.BooleanFieldDef;
import com.hd123.rumba.gwt.widget2.client.fielddef.EnumFieldDef;
import com.hd123.rumba.gwt.widget2.client.fielddef.FieldDef;
import com.hd123.rumba.gwt.widget2.client.form.RComboBox.Options.Option;

/**
 * 带有选项的文本输入框。
 * <p>
 * 
 * <b>控件元素结构</b><br>
 * div@super<br>
 * ..label@super<br>
 * ..div class=field@super<br>
 * ....input type=text class=input]<br>
 * ....DropButton<br>
 * ....[? class=buttons]@super<br>
 * ....[div class=suffix]@super<br>
 * ....[? class=tip]@super<br>
 * ....[div class=note]@super
 * <p>
 * 
 * <b>CSS Style Rules</b> <ol type=disc>
 * <li>.rb-ComboBox {}</li>
 * <li>.rb-ComboBox-disabled {}</li>
 * <li>.rb-ComboBox <i>Super</i> {}</li>
 * <li>.rb-ComboBox .dropButton {}</li>
 * <li>.rb-ComboBox .dropButton-hovering {}</li>
 * <li>.rb-ComboBox .dropButton-focused {}</li>
 * <li>.rb-ComboBox .dropButton-focused-hovering {}</li>
 * <li>.rb-ComboBox .dropButton-frozen {}</li>
 * <li>.rb-ComboBox .dropButton-frozen-focused {}</li>
 * <li>.rb-ComboBox-popup {}</li>
 * <li>.rb-ComboBox-popup .item {}</li>
 * <li>.rb-ComboBox-popup .item-selected {}</li>
 * </ol>
 * 
 * TODO 根据可选值，建议录入功能。
 * 
 * @author lxm
 * 
 */
public class RComboBox extends RTextBoxBase {

  private static final String STYLENAME_DEFAULT = "rb-ComboBox";
  private static final String EMPTYTEXT_DEFAULT = "请选择...";

  public static final Style DROPDOWN = Style.DROPDOWN;
  public static final Style DROPDOWNLIST = Style.DROPDOWNLIST;

  public static final String BOOLEAN_CAPTION_TRUE = "是";
  public static final String BOOLEAN_CAPTION_FALSE = "否";

  /**
   * 创建对象实例。
   */
  public RComboBox() {
    super(new RTextInput());
    setEmptyText(EMPTYTEXT_DEFAULT);

    dropButton = new DropButton();
    addButton(dropButton);

    Handler handler = new Handler();
    addFocusHandler(handler);
    addBlurHandler(handler);
    addKeyDownHandler(handler);

    getInput().addMouseDownHandler(new InputHandler());

    setStyleName(STYLENAME_DEFAULT);
  }

  /**
   * @param fieldDef
   *          字段定义
   */
  public RComboBox(FieldDef fieldDef) {
    this();
    setFieldDef(fieldDef);
  }

  private boolean readOnly = false;
  private DropButton dropButton;
  private Options options = new Options();
  private PopupList popup;
  private int maxListHeight = 150;
  private Style style = DROPDOWN;

  /**
   * 取得对应的可选项集合对象。
   * 
   * @return
   */
  public Options getOptions() {
    return options;
  }

  /** 最大可选值列表显示高度。默认为150px。 */
  public int getMaxListHeight() {
    return maxListHeight;
  }

  /** 最大可选值列表显示高度 */
  public void setMaxListHeight(int maxListHeight) {
    this.maxListHeight = maxListHeight;
  }

  /**
   * ComboBox风格，默认为dropDown
   */
  public Style getStyle() {
    return style;
  }

  /**
   * 设置ComboBox风格，默认为dropDown
   */
  public void setStyle(Style style) {
    this.style = style;
    refreshInput();
  }

  /**
   * 取得当前本文输入框中的显示文本，如果当前选择了空值选项，则返回null。
   * 
   */
  public String getText() {
    return getValue() == null ? null : super.getText();
  }

  /**
   * 设置文本输入框中的显示文本。
   * 
   * @throws RuntimeException
   *           当{@link #DROPDOWNLIST}风格时，如果指定了未知的显示本文抛出异常。
   */
  public void setText(String text) throws RuntimeException {
    if (style == DROPDOWNLIST && text != null) {
      int index = options.indexOfDisplayText(text);
      if (index < 0)
        throw new RuntimeException("指定的不是已知的可选值(" + text + ")。");
    }
    super.setText(text);
  }

  /**
   * 取得当前取值，已经将显示本文转换为值。
   * 
   * @return
   */
  public Object getValue() {
    String displayText = super.getText();

    Option option = options.getByDisplayText(displayText);
    if (option != null)
      return option.value;
    else
      return style == DROPDOWNLIST ? null : displayText;
  }

  /**
   * 设置当前取值，指定的为可选项的值，而不是显示文本。
   * 
   * @param value
   * @throws RuntimeException
   *           当{@link #DROPDOWNLIST}风格时，如果指定了未知的值抛出异常。
   */
  public void setValue(Object value) throws RuntimeException {
    int index = options.indexOf(value);
    if (index < 0) {
      if (style == DROPDOWNLIST && value != null)
        throw new RuntimeException("指定的不是已知的可选值(" + value + ")。");
      super.setText(value == null ? "" : value.toString());
    } else {
      String displayText = options.getDisplayText(index);
      super.setText(displayText);
    }
  }

  /**
   * 清空取值。清空后getValue()将返回null。
   */
  public void clearValue() {
    setValue(null);
  }

  /**
   * 取得当前值的String形式
   * 
   * @return
   */
  public String getValueAsString() {
    Object value = getValue();
    return value == null ? null : value.toString();
  }

  /**
   * 取得当前值的，若当前值不是Integer类型则返回null
   * 
   * @return
   */
  public Integer getValueAsInteger() {
    Object value = getValue();
    return value instanceof Integer ? (Integer) value : null;
  }

  /**
   * 取得当前值的，若当前值不是Double类型则返回null
   * 
   * @return
   */
  public Double getValueAsDouble() {
    Object value = getValue();
    return value instanceof Double ? (Double) value : null;
  }

  /**
   * 取得当前值的，若当前值不是JsBigDecimal类型则返回null
   * 
   * @return
   */
  public JsBigDecimal getValueAsBigDecimal() {
    Object value = getValue();
    return value instanceof JsBigDecimal ? (JsBigDecimal) value : null;
  }

  /**
   * 取得当前值的，若当前值不是Boolean类型则返回null
   * 
   * @return
   */
  public Boolean getValueAsBoolean() {
    Object value = getValue();
    return value instanceof Boolean ? (Boolean) value : null;
  }

  /**
   * 取得当前值的，若当前值不是Date类型则返回null
   * 
   * @return
   */
  public Date getValueAsDate() {
    Object value = getValue();
    return value instanceof Date ? (Date) value : null;
  }

  public void setEnabled(boolean enabled) {
    super.setEnabled(enabled);
    refreshDropButton();
  }

  public boolean isReadOnly() {
    return readOnly;
  }

  public void setReadOnly(boolean readOnly) {
    this.readOnly = readOnly;
    refreshInput();
    refreshDropButton();
  }

  private void refreshInput() {
    getInput().setReadOnly(readOnly || style == DROPDOWNLIST);
    DOM.setStyleAttribute(getInput().getElement(), "cursor",
        style == DROPDOWNLIST && !readOnly ? "hand" : "");
    refreshInputStyle();
    refreshEmptyText();
  }

  public void clearButtons() {
    super.clearButtons();
    addButton(dropButton);
  }

  public void setFieldDef(FieldDef fieldDef) {
    super.setFieldDef(fieldDef);

    if (fieldDef instanceof EnumFieldDef) {
      setStyle(DROPDOWNLIST);
      String[][] values = ((EnumFieldDef) fieldDef).getValues();
      options.clear();
      for (int i = 0; i < values.length; i++) {
        options.add(values[i][0], values[i][1]);
      }
    } else if (fieldDef instanceof BooleanFieldDef) {
      setStyle(DROPDOWNLIST);
      options.clear();
      options.add(Boolean.TRUE, BOOLEAN_CAPTION_TRUE);
      options.add(Boolean.FALSE, BOOLEAN_CAPTION_FALSE);
    }

    if (fieldDef.isNullable())
      options.setNullOptionTextToDefault();
  }

  protected int onKeyEnter() {
    if (popup != null && popup.isShowing()) {
      popup.clickSelectedItem();
      return KeyCodes.KEY_ENTER;
    } else
      return super.onKeyEnter();
  }

  private void refreshDropButton() {
    dropButton.setFrozen(!isEnabled() || isReadOnly());
  }

  private PopupList getPopup() {
    if (popup == null)
      popup = new PopupList();
    return popup;
  }

  /**
   * RComboBox的可选项集合对象。
   */
  public class Options {

    private ArrayList options = new ArrayList();
    private boolean changed = false;

    /** 默认的空值选项的文本 */
    public static final String DEFAULT_NULL_OPTION = "[空]";

    /** 空值选项 */
    private Option nullOption = null;

    class Option {
      public Object value;
      public String displayText;

      public Option() {
        super();
      }

      public Option(Object value) {
        this(value, null);
      }

      public Option(Object value, String displayText) {
        super();
        this.value = value;
        if (displayText == null)
          displayText = value == null ? "" : value.toString();
        this.displayText = displayText;
      }
    }

    boolean isChanged() {
      return changed;
    }

    void setChanged(boolean changed) {
      this.changed = changed;
    }

    /**
     * 添加可选值在最后。
     * 
     * @param value
     *          指定被添加的取值。显示文本由系统默认提供。
     */
    public void add(Object value) throws RuntimeException {
      Option opt = new Option(value);
      options.add(opt);
      changed = true;
    }

    /**
     * 添加可选值在最后。
     * 
     * @param value
     *          指定被添加的取值。
     * @param displayText
     *          指定显示文本。
     */
    public void add(Object value, String displayText) throws RuntimeException {
      options.add(new Option(value, displayText));
      changed = true;
    }

    /**
     * 清除所有可选值。不包括空值选项，要删除空值选项，请调用removeNullOption()。
     */
    public void clear() {
      if (nullOption == null) {
        options.clear();
      } else {
        // 删除第0条以外的其它项
        for (int i = options.size() - 1; i >= 1; i--)
          options.remove(i);
      }
      changed = true;
    }

    /**
     * 添加可选值在指定的位置。
     * 
     * @param value
     *          指定被添加的取值。显示文本由系统默认提供。
     * @param index
     *          指定添加的位置。空值选项不占用位置，即第0个位置是正式的选项而非空值选项。
     */
    public void insert(Object value, int index) throws RuntimeException {
      Option opt = new Option(value);
      options.add(nullOption == null ? index : index + 1, opt);
      changed = true;
    }

    /**
     * 添加可选值在指定的位置。
     * 
     * @param value
     *          指定被添加的取值。
     * @param displayText
     *          指定显示文本。
     * @param index
     *          指定添加的位置。空值选项不占用位置，即第0个位置是正式的选项而非空值选项。
     */
    public void insert(Object value, String displayText, int index) throws RuntimeException {
      options.add(nullOption == null ? index : index + 1, new Option(value, displayText));
      changed = true;
    }

    /**
     * 删除指定可选值。不包括空值选项，要删除空值选项，请调用removeNullOption()。
     * 
     * @param index
     *          指定索引。
     */
    public void remove(int index) {
      options.remove(nullOption == null ? index : index + 1);
      changed = true;
    }

    /**
     * 删除指定可选值。
     * 
     * @param value
     *          指定可选项的取值。
     * @return 若指定的取值并不存在则返回false，否则返回true。
     */
    public boolean remove(Object value) {
      Option opt = getByValue(value);
      if (opt == null)
        return false;
      changed = true;
      return options.remove(opt);
    }

    /**
     * 取得所包含可选项的数量。包括空值选项。
     * 
     * @return
     */
    public int size() {
      return options.size();
    }

    /**
     * 取得所包含可选项的数量。不包括空值选项。
     * 
     * @return
     */
    public int sizeWithoutNullOption() {
      return nullOption == null ? options.size() : options.size() - 1;
    }

    /**
     * 取得指定可选项值的索引。
     * 
     * @param value
     *          指定可选项的取值。
     * @return
     */
    public int indexOf(Object value) {
      for (int i = 0, n = options.size(); i < n; i++) {
        Option opt = (Option) options.get(i);
        if (value == null) {
          if (opt.value == null)
            return i;
        } else {
          if (value.equals(opt.value))
            return i;
        }
      }
      return -1;
    }

    /**
     * 取得指定可选项值的索引。
     * 
     * @param displayText
     *          指定可选项的显示文本。
     * @return
     */
    public int indexOfDisplayText(String displayText) {
      for (int i = 0, n = options.size(); i < n; i++) {
        Option opt = (Option) options.get(i);
        if (displayText == null) {
          if (opt.displayText == null)
            return i;
        } else {
          if (displayText.equals(opt.displayText))
            return i;
        }
      }
      return -1;
    }

    /**
     * 取得指定索引可选项得取值。
     * 
     * @param index
     * @return
     */
    public Object getValue(int index) {
      Option opt = (Option) options.get(index);
      return opt.value;
    }

    /**
     * 根据显示本文取得取值。
     * 
     * @param displayText
     * @return
     */
    public Object getValue(String displayText) {
      Option opt = getByDisplayText(displayText);
      return opt == null ? null : opt.value;
    }

    /**
     * 取得指定索引可选项的显示文本。
     * 
     * @param index
     * @return
     */
    public String getDisplayText(int index) {
      Option opt = (Option) options.get(index);
      return opt.displayText;
    }

    /**
     * 判断是否已经包含有指定的显示文本。
     * 
     * @param displayText
     * @return
     */
    public boolean hasDisplayText(String displayText) {
      return getByDisplayText(displayText) != null;
    }

    private Option getByValue(Object value) {
      for (int i = 0, n = options.size(); i < n; i++) {
        Option opt = (Option) options.get(i);
        if (value == null) {
          if (opt.value == null)
            return opt;
        } else {
          if (value.equals(opt.value))
            return opt;
        }
      }
      return null;
    }

    Option getByDisplayText(String displayText) {
      for (int i = 0, n = options.size(); i < n; i++) {
        Option opt = (Option) options.get(i);
        if (displayText == null) {
          if (opt.displayText == null)
            return opt;
        } else {
          if (displayText.equals(opt.displayText))
            return opt;
        }
      }
      return null;
    }

    /**
     * 设置空值选项的文本
     * 
     * @param nullOptionText
     * 
     */
    public void setNullOptionText(String nullOptionText) {
      if (nullOptionText == null)
        throw new NullPointerException("nullOptionText不能为空");

      if (nullOption == null) {
        nullOption = new Option(null, nullOptionText);
        options.add(0, nullOption);
      } else {
        nullOption.displayText = nullOptionText;
      }

      changed = true;
    }

    /**
     * 设置空值选项的文本为默认的默认的{@link #DEFAULT_NULL_OPTION}
     * 
     */
    public void setNullOptionTextToDefault() {
      setNullOptionText(DEFAULT_NULL_OPTION);
    }

    /**
     * 删除空值选项
     * 
     */
    public void removeNullOption() {
      if (nullOption != null) {
        options.remove(nullOption);
        nullOption = null;
        changed = true;
      }
    }
  }

  /**
   * ComboBox的风格定义。
   * 
   * @author lxm
   * 
   */
  public static class Style {

    public static final Style DROPDOWN = new Style("dropDown");
    public static final Style DROPDOWNLIST = new Style("dropDownList");

    private String value;

    private Style(String value) {
      super();
      this.value = value;
    }

    public String getValue() {
      return value;
    }
  }

  /**
   * 自身的事件处理
   * 
   * @author LiQi
   * 
   */
  private class Handler implements FocusHandler, BlurHandler, KeyDownHandler {

    public void onFocus(FocusEvent event) {
      dropButton.setFocused(true);
    }

    public void onBlur(BlurEvent event) {
      dropButton.setFocused(false);
    }

    public void onKeyDown(KeyDownEvent event) {
      boolean showing = popup == null ? false : popup.isShowing();

      switch (event.getNativeKeyCode()) {
      case KeyCodes.KEY_DOWN:
        if (!showing)
          dropButton.doClick();
        else {
          if (event.isControlKeyDown())
            popup.selectOffset(options.size());
          else
            popup.selectOffset(1);
        }
        break;

      case KeyCodes.KEY_UP:
        if (showing) {
          if (event.isControlKeyDown())
            popup.selectOffset(-1 * options.size());
          else
            popup.selectOffset(-1);
        }
        break;

      case KeyCodes.KEY_ESCAPE:
        if (showing)
          popup.hide();
        break;
      }
    }
  }

  /**
   * 文本框事件处理
   * 
   * @author LiQi
   * 
   */
  private class InputHandler implements MouseDownHandler {
    public void onMouseDown(MouseDownEvent event) {
      if (style == DROPDOWNLIST && !readOnly && isEnabled())
        dropButton.doClick();
    }
  }

  /**
   * RComboBox的向下按钮。
   * 
   * @author lxm
   * 
   */
  private class DropButton extends Widget implements IsRButton {

    public DropButton() {
      super();
      setElement(DOM.createDiv());
      setStyleName("dropButton");
      sinkEvents(Event.ONCLICK | Event.MOUSEEVENTS);
    }

    private boolean hovering = false;
    private boolean focused = false;
    private boolean frozen = false;

    public boolean isShowIcon() {
      return true;
    }

    public void setShowIcon(boolean showIcon) {
      // Do Nothing
    }

    public boolean isShowText() {
      return false;
    }

    public void setShowText(boolean showText) {
      // Do Nothing
    }

    /** 是否获得焦点。 */
    public boolean isFocused() {
      return focused;
    }

    /** 是否获得焦点。 */
    public void setFocused(boolean focused) {
      this.focused = focused;
      refreshStyle();
    }

    /** 是否被冻结。有两种情况导致冻结：一种是输入框disabled，另一种是输入框readonly。 */
    public boolean isFrozen() {
      return frozen;
    }

    /** 是否被冻结。有两种情况导致冻结：一种是输入框disabled，另一种是输入框readonly。 */
    public void setFrozen(boolean frozen) {
      this.frozen = frozen;
      if (this.frozen)
        DOM.setElementAttribute(getElement(), "disabled", "true");
      else
        DOM.removeElementAttribute(getElement(), "disabled");
      refreshStyle();
    }

    public void onBrowserEvent(Event event) {
      super.onBrowserEvent(event);

      if (isFrozen())
        return;

      switch (DOM.eventGetType(event)) {
      case Event.ONMOUSEDOWN:
        // 按照Windows惯例，下拉框都是在MouseDown时就弹出
        doClick();
        break;
      case Event.ONMOUSEOVER:
        setHovering(true);
        break;
      case Event.ONMOUSEOUT:
        setHovering(false);
        break;
      }
    }

    public void doClick() {
      if (options.isChanged())
        getPopup().refresh();
      getPopup().popup();

      // 点击下拉按钮后，将焦点置于文本框，以便处理键盘事件
      RComboBox.this.setFocus(false);
      DeferredCommand.addCommand(new Command() {
        public void execute() {
          RComboBox.this.setFocus(true);
        }
      });
    }

    private void setHovering(boolean hovering) {
      this.hovering = hovering;
      refreshStyle();
    }

    private void refreshStyle() {
      StringBuffer sb = new StringBuffer();
      sb.append(getStylePrimaryName());
      sb.append(" ");
      sb.append(getStylePrimaryName());
      if (frozen)
        sb.append("-frozen");
      if (focused)
        sb.append("-focused");
      if (!frozen && hovering)
        sb.append("-hovering");
      setStyleName(sb.toString());
    }

  }

  /**
   * RComboBox的弹出式可选值列表。
   * 
   * @author lxm
   * 
   */
  private class PopupList extends PopupPanel implements CloseHandler {

    public PopupList() {
      super(true);
      drawSelf();
    }

    private ScrollPanel pnl;
    private FlowPanel pnlList;
    private PopupItem selectedItem;
    private boolean showing = false;

    /**
     * 弹出显示列表。
     */
    public void popup() {
      // 如果没有可选值，则不显示可选列表。
      if (options.size() == 0)
        return;

      setPopupPositionAndShow(new PositionCallback() {
        public void setPosition(int offsetWidth, int offsetHeight) {
          // 计算并设置尺寸
          int width = getInput().getOffsetWidth() + dropButton.getOffsetWidth();
          setWidth(width + "px");
          int height = getOffsetHeight();
          if (height > maxListHeight) {
            setHeight(maxListHeight + "px");
            height = maxListHeight;
          }

          // 计算并设置位置
          int left = getInput().getAbsoluteLeft();
          int top = getInput().getAbsoluteTop() + getInput().getOffsetHeight();
          if (top + height > Window.getScrollTop() + Window.getClientHeight()) {
            int top2 = getInput().getAbsoluteTop() - height;
            if (top2 > Window.getScrollTop())
              top = top2;
          }

          if (pnlList.getWidgetCount() > 0)
            setSelectedItem((PopupItem) pnlList.getWidget(0), true);

          setPopupPosition(left, top);
        }
      });
    }

    /**
     * 刷新可选项的显示。
     */
    public void refresh() {
      // 清除所有pnlList包含的Widget。
      for (int i = pnlList.getWidgetCount() - 1; i >= 0; i--)
        pnlList.remove(i);
      selectedItem = null;

      for (int i = 0, n = options.size(); i < n; i++) {
        String displayText = options.getDisplayText(i);
        PopupItem item = new PopupItem(this);
        item.setHTML(displayText);
        item.setValueIndex(i);
        pnlList.add(item);
      }
    }

    /**
     * 取得当前列表是否处于显示的状态。
     * 
     * @return
     */
    public boolean isShowing() {
      return showing;
    }

    public void show() {
      super.show();
      showing = true;
    }

    /**
     * 相对于当前选中的可选项，选中其指定偏移量的那个可选项。
     * 
     * @param offset
     */
    public void selectOffset(int offset) {
      if (offset == 0)
        return;
      int oldIndex = pnlList.getWidgetIndex(selectedItem);
      int index = oldIndex;
      if (index < 0) {
        if (pnlList.getWidgetCount() > 0) {
          if (offset > 0)
            index = 0;
          else
            index = pnlList.getWidgetCount() - 1;
        }
      } else {
        index += offset;
        if (index < 0)
          index = 0;
        else if (index > pnlList.getWidgetCount() - 1)
          index = pnlList.getWidgetCount() - 1;
      }
      if (index >= 0)
        setSelectedItem((PopupItem) pnlList.getWidget(index), true);
    }

    public void onClose(CloseEvent event) {
      showing = false;
    }

    /**
     * 模拟点击当前选中的可选项的动作。
     */
    public void clickSelectedItem() {
      if (selectedItem != null)
        doClick(selectedItem);
    }

    private void drawSelf() {
      pnl = new ScrollPanel();
      pnlList = new FlowPanel();
      pnl.setWidget(pnlList);
      setWidget(pnl);
      addCloseHandler(this);
      setStyleName(RComboBox.this.getStylePrimaryName() + "-popup");
    }

    void setSelectedItem(PopupItem selItem, boolean ensureVisible) {
      if (selectedItem == selItem)
        return;
      if (selectedItem != null)
        selectedItem.setSelected(false);
      selectedItem = selItem;
      if (selectedItem != null) {
        selectedItem.setSelected(true);
        if (ensureVisible) {
          DOM.scrollIntoView(selectedItem.getElement());
          pnl.setHorizontalScrollPosition(0);
        }
      }
    }

    private void doClick(PopupItem item) {
      assert item != null;
      hide();
      int index = item.getValueIndex();
      String displayText = options.getDisplayText(index);
      setText(displayText);
      getInput().setFocus(true);
      fireChange();
    }

  }

  /**
   * RComboBox的弹出式可选值项。
   * 
   * @author lxm
   * 
   */
  private class PopupItem extends HTML {

    public PopupItem(PopupList owner) {
      super();
      assert owner != null;
      this.owner = owner;
      setStyleName("item");
      sinkEvents(Event.MOUSEEVENTS | Event.ONCLICK);
    }

    private PopupList owner;
    private int valueIndex = -1;
    private boolean selected = false;

    public int getValueIndex() {
      return valueIndex;
    }

    public void setValueIndex(int valueIndex) {
      this.valueIndex = valueIndex;
    }

    public boolean isSelected() {
      return selected;
    }

    public void setSelected(boolean selected) {
      this.selected = selected;
      if (this.selected)
        addStyleDependentName("selected");
      else
        removeStyleDependentName("selected");
    }

    public void onBrowserEvent(Event event) {
      super.onBrowserEvent(event);

      switch (DOM.eventGetType(event)) {
      case Event.ONCLICK:
        doClick();
        break;
      case Event.ONMOUSEOVER:
        owner.setSelectedItem(this, false);
        break;
      }
    }

    public void doClick() {
      owner.hide();
      String displayText = options.getDisplayText(valueIndex);
      RComboBox.this.setText(displayText);
      getInput().setFocus(true);
      fireChange();
    }
  }
}
